<html>
  <head>
    <meta charset="utf-8" />
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Android 混淆 | Jiang</title>
<link rel="shortcut icon" href="https://cherrylover.github.io/favicon.ico?v=1601447716515">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="stylesheet" href="https://cherrylover.github.io/styles/main.css">

<script src="https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.23.0/moment.min.js"></script>


<script async src="https://www.googletagmanager.com/gtag/js?id=UA-143284233-1"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-143284233-1');
</script>

  </head>
  <body>
    <div class="main">
      <div class="main-content">
        <div class="site-header">
  <a href="https://cherrylover.github.io">
  <img class="avatar" src="https://cherrylover.github.io/images/avatar.png?v=1601447716515" alt="">
  </a>
  <h1 class="site-title">
    Jiang
  </h1>
  <p class="site-description">
    我允许你走进我的世界，但不许你在我的世界里走来走去。
  </p>
  <div class="menu-container">
    
      
        <a href="/" class="menu">
          首页
        </a>
      
    
      
        <a href="/archives" class="menu">
          归档
        </a>
      
    
      
        <a href="/tags" class="menu">
          标签
        </a>
      
    
      
        <a href="https://cherrylover.github.io/post/guan-yu-wo/" class="menu">
          关于
        </a>
      
    
  </div>
  <div class="social-container">
    
      
        <a href="https://github.com/CherryLover" target="_blank">
          <i class="fab fa-github"></i>
        </a>
      
    
      
    
      
        <a href="https://juejin.im/user/576d73cd0a2b58006a09ad87" target="_blank">
          <i class="fab fa-weibo"></i>
        </a>
      
    
      
    
      
    
  </div>
</div>

      
        <div class="post-detail">
          <article class="post">
            <h2 class="post-title">
              Android 混淆
            </h2>
            <div class="post-info">
              <time class="post-time">
                · 2019-07-15 ·
              </time>
              
                <a href="https://cherrylover.github.io/tag/R_L3US7TO/" class="post-tags">
                  # 转载
                </a>
              
                <a href="https://cherrylover.github.io/tag/37yorOBW4/" class="post-tags">
                  # 持续学习
                </a>
              
                <a href="https://cherrylover.github.io/tag/Mk4HmBIQ7/" class="post-tags">
                  # Android
                </a>
              
            </div>
            
              <div class="post-feature-image" style="background-image: url('https://monster-image-backup.oss-cn-shanghai.aliyuncs.com/picgo/blog/blog_Android混淆.jpg')">
              </div>
            
            <div class="post-content">
              <blockquote>
<p>本篇文章的主要内容是本人在学习 Android 混淆时觉得不错的文章。特地收藏过来，原文链接：https://www.diycode.cc/topics/380。</p>
</blockquote>
<p>一直以来知道 Android 混淆，但是都没上手用过，大概，好像知道点东西，但又搞不清，干脆来好好学一下。</p>
<p>我们说混淆一般都是说在 ProGuard 文件中我们定义的内容，那定义在 ProGuard 内容有什么作用呢？我问了一些人，得到了下面几个答案，来看看吧。</p>
<ol>
<li>能够在编译时修改源代码使其难以被阅读而且能够做代码压缩；</li>
<li>检测和移除封装应用中未使用的类、字段、方法和属性，包括第三方代码库中的未使用项（这使其成为以变通方式解决 64k 引用限制的有用工具）；</li>
<li>优化字节码，移除未使用的代码指令；</li>
<li>用短名称混淆其余的类、字段和方法；</li>
<li>混淆过的代码可令 APK 难以被逆向工程；</li>
<li>APK 能够被压缩，而且压缩的幅度较大，能帮助你的应用更容易被用户接收。同时，APK 体积也会影响安装和冷启动速度，越大越慢。</li>
</ol>
<p>看到这点多优点，我实在按捺不住了，于是我开始新的征途，下面正文开始。</p>
<h2 id="写在前面">写在前面</h2>
<p>大家好，我是光源。</p>
<p>本文首发于我的个人公众账号，同时会在个人博客上同步。如果博客本身有修改或勘误，会在博客更新。</p>
<h2 id="综述">综述</h2>
<p>毫无疑问，混淆是打包过程中最重要的流程之一，在没有特殊原因的情况下，所有 app 都应该开启混淆。</p>
<p>首先，这里说的的混淆其实是包括了代码压缩、代码混淆以及资源压缩等的优化过程。依靠 ProGuard，混淆流程将主项目以及依赖库中未被使用的类、类成员、方法、属性移除，这有助于规避64K方法数的瓶颈；同时，将类、类成员、方法重命名为无意义的简短名称，增加了逆向工程的难度。而依靠 Gradle 的 Android 插件，我们将移除未被使用的资源，可以有效减小 apk 安装包大小。</p>
<p>本文由两部分构成，第一部分给出混淆的最佳实践，力求让零基础的新手都可以直接使用混淆；第二部分会介绍一下混淆的整体、自定义混淆规则的语法与实践、自定义资源保持的规则等。</p>
<h2 id="一-android混淆最佳实践">一、Android混淆最佳实践</h2>
<h3 id="1-混淆配置">1. 混淆配置</h3>
<p>一般情况下，app module 的 <code>build.gradle</code> 文件默认会有如下结构：</p>
<pre><code>android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
</code></pre>
<p>因为开启混淆会使编译时间变长，所以<code>debug</code>模式下不应该开启。我们需要做的是：</p>
<ol>
<li>将<code>release</code>下<code>minifyEnabled</code>的值改为<code>true</code>，打开混淆；</li>
<li>加上<code>shrinkResources true</code>，打开资源压缩。</li>
</ol>
<p>修改后文件内容如下：</p>
<pre><code>android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
</code></pre>
<h3 id="2-自定义混淆规则">2. 自定义混淆规则</h3>
<p>在 <code>app module</code> 下默认生成了项目的自定义混淆规则文件 <code>proguard-rules.pro</code>，多方调研后，一份适用于大部分项目的混淆规则最佳实践如下：</p>
<pre><code>#指定压缩级别
-optimizationpasses 5

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
-useuniqueclassmembernames

#优化时允许访问并修改有修饰符的类和类的成员 
-allowaccessmodification

#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable

#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

#Fragment不需要在AndroidManifest.xml中注册，需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment

# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
</code></pre>
<p>真正通用的、需要添加的就是上面这些，除此之外，需要每个项目根据自身的需求添加一些混淆规则：</p>
<ul>
<li>第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则，使用时注意添加。</li>
<li>在运行时动态改变的代码，例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model包下的话，可以添加类似这样的代码把所有实体类都保持住：-keep public class <strong>.<em>Model</em>.</strong> {*;}<br>
- JNI中调用的类。<br>
- WebView中JavaScript调用的方法<br>
- Layout布局使用的View构造函数、android:onClick等。</li>
</ul>
<h3 id="3-检查混淆结果">3. 检查混淆结果</h3>
<p>混淆过的包必须进行检查，避免因混淆引入的bug。</p>
<p>一方面，需要从代码层面检查。使用上文的配置进行混淆打包后在 <module-name>/build/outputs/mapping/release/ 目录下会输出以下文件：<br>
- dump.txt <br>
描述APK文件中所有类的内部结构<br>
- mapping.txt<br>
提供混淆前后类、方法、类成员等的对照表<br>
- seeds.txt<br>
列出没有被混淆的类和成员<br>
- usage.txt<br>
列出被移除的代码</p>
<p>我们可以根据 <code>seeds.txt</code> 文件检查未被混淆的类和成员中是否已包含所有期望保留的，再根据 <code>usage.txt</code> 文件查看是否有被误移除的代码。</p>
<p>另一方面，需要从测试方面检查。将混淆过的包进行全方面测试，检查是否有 bug 产生。</p>
<h3 id="4-解出混淆栈">4. 解出混淆栈</h3>
<p>混淆后的类、方法名等等难以阅读，这固然会增加逆向工程的难度，但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位，这时需要将混淆反解。</p>
<p>在 <code>&lt;sdk-root&gt;/tools/proguard/</code> 路径下有附带的的反解工具（Window 系统为 <code>proguardgui.bat</code>，Mac 或 Linux 系统为 <code>proguardgui.sh</code>）。</p>
<p>这里以 Window 平台为例。双击运行 <code>proguardgui.bat</code> 后，可以看到左侧的一行菜单。点击 <code>ReTrace</code>，选择该混淆包对应的 mapping 文件（混淆后在 <code>&lt;module-name&gt;/build/outputs/mapping/release/</code> 路径下会生成 <code>mapping.txt</code> 文件，它的作用是提供混淆前后类、方法、类成员等的对照表），再将 crash 的 <code>stack trace</code> 黏贴进输入框中，点击右下角的 <code>ReTrace</code> ，混淆后的堆栈信息就显示出来了。</p>
<p>以上使用 GUI 程序进行操作，另一种方式是利用该路径下的 <code>retrace</code> 工具通过命令行进行反解，命令是</p>
<pre><code class="language-shell">retrace.bat|retrace.sh [-verbose] mapping.txt [&lt;stacktrace_file&gt;]
</code></pre>
<p>例如：</p>
<pre><code class="language-shell">retrace.bat -verbose mapping.txt obfuscated_trace.txt
</code></pre>
<h3 id="注意事项">注意事项：</h3>
<ol>
<li>
<p>所有在 <code>AndroidManifest.xml</code> 涉及到的类已经自动被保持，因此不用特意去添加这块混淆规则。（很多老的混淆文件里会加，现在已经没必要）</p>
</li>
<li>
<p><code>proguard-android.txt</code> 已经存在一些默认混淆规则，没必要在 <code>proguard-rules.pro</code> 重复添加，该文件具体规则见附录1：</p>
</li>
</ol>
<h2 id="二-混淆简介">二、混淆简介</h2>
<p>Android中的“混淆”可以分为两部分，一部分是 Java 代码的优化与混淆，依靠 proguard 混淆器来实现；另一部分是资源压缩，将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系，但一般我们都会放一起讲)。</p>
<h3 id="1-代码压缩">1. 代码压缩</h3>
<figure data-type="image" tabindex="1"><a href="http://zhenghuiy-blog.qiniudn.com/proguard-process.png"><img src="http://zhenghuiy-blog.qiniudn.com/proguard-process.png" alt="img" loading="lazy"></a></figure>
<p>代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示，混淆过程会有如下几个功能：</p>
<ol>
<li>压缩。移除无效的类、类成员、方法、属性等；</li>
<li>优化。分析和优化方法的二进制代码；根据proguard-android-optimize.txt中的描述，优化可能会造成一些潜在风险，不能保证在所有版本的Dalvik上都正常运行。</li>
<li>混淆。把类名、属性名、方法名替换为简短且无意义的名称；</li>
<li>预校验。添加预校验信息。这个预校验是作用在Java平台上的，Android平台上不需要这项功能，去掉之后还可以加快混淆速度。</li>
</ol>
<p>这四个流程默认开启。</p>
<p>在 Android 项目中我们可以选择将“优化”和“预校验”关闭，对应命令是<code>-dontoptimize</code>、<code>-dontpreverify</code>（当然，默认的 <code>proguard-android.txt</code> 文件已包含这两条混淆命令，不需要开发者额外配置）。</p>
<h3 id="2-资源压缩">2. 资源压缩</h3>
<p>资源压缩将移除项目及依赖的库中未被使用的资源，这在减少 apk 包体积上会有不错的效果，一般建议开启。具体做法是在 <code>build.grade</code> 文件中，将 <code>shrinkResources</code> 属性设置为 <code>true</code>。需要注意的是，<strong>只有在用minifyEnabled true开启了代码压缩后，资源压缩才会生效</strong>。</p>
<p>资源压缩包含了“合并资源”和“移除资源”两个流程。</p>
<p>“合并资源”流程中，名称相同的资源被视为重复资源会被合并。需要注意的是，这一流程不受shrinkResources属性控制，也无法被禁止， gradle 必然会做这项工作，因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源：<br>
- src/main/res/ 路径</p>
<ul>
<li>不同的构建类型（debug、release等等）</li>
<li>不同的构建渠道</li>
<li>项目依赖的第三方库</li>
</ul>
<p>合并资源时按照如下优先级顺序：</p>
<pre><code>依赖 -&gt; main -&gt; 渠道 -&gt; 构建类型
</code></pre>
<p>举个例子，假如重复资源同时存在于<code>main</code>文件夹和不同渠道中，gradle 会选择保留渠道中的资源。</p>
<p>同时，如果重复资源在同一层次出现，比如<code>src/main/res/</code> 和 <code>src/main/res2/</code>，则 gradle 无法完成资源合并，这时会报资源合并错误。</p>
<p>“移除资源”流程则见名知意，需要注意的是，类似代码，混淆资源移除也可以定义哪些资源需要被保留，这点在下文给出。</p>
<h2 id="三-自定义混淆规则">三、自定义混淆规则</h2>
<p>在上文“混淆配置”中有这样一行代码</p>
<pre><code>proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
</code></pre>
<p>这行代码定义了混淆规则由两部分构成：位于 SDK 的 <code>tools/proguard/</code> 文件夹中的 <code>proguard-android.txt</code> 的内容以及默认放置于模块根目录的 <code>proguard-rules.pro</code> 的内容。前者是 SDK 提供的默认混淆文件（内容见附录1），后者是开发者自定义混淆规则的地方。</p>
<h3 id="1-常见混淆命令">1. 常见混淆命令：</h3>
<ul>
<li>optimizationpasses</li>
<li>dontoptimize</li>
<li>dontusemixedcaseclassnames</li>
<li>dontskipnonpubliclibraryclasses</li>
<li>dontpreverify</li>
<li>dontwarn</li>
<li>verbose</li>
<li>optimizations</li>
<li>keep</li>
<li>keepnames</li>
<li>keepclassmembers</li>
<li>keepclassmembernames</li>
<li>keepclasseswithmembers</li>
<li>keepclasseswithmembernames</li>
</ul>
<p>在第一部分 Android 混淆最佳实践中已介绍部分需要使用到的混淆命令，这里不再赘述，详情请查阅 <a href="http://proguard.sourceforge.net/">官网</a>。需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令：</p>
<table>
<thead>
<tr>
<th style="text-align:left">命令</th>
<th style="text-align:left">作用</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">-keep</td>
<td style="text-align:left">防止类和成员被移除或者被重命名</td>
</tr>
<tr>
<td style="text-align:left">-keepnames</td>
<td style="text-align:left">防止类和成员被重命名</td>
</tr>
<tr>
<td style="text-align:left">-keepclassmembers</td>
<td style="text-align:left">防止成员被移除或者被重命名</td>
</tr>
<tr>
<td style="text-align:left">-keepnames</td>
<td style="text-align:left">防止成员被重命名</td>
</tr>
<tr>
<td style="text-align:left">-keepclasseswithmembers</td>
<td style="text-align:left">防止<strong>拥有该成员的</strong>类和成员被移除或者被重命名</td>
</tr>
<tr>
<td style="text-align:left">-keepclasseswithmembernames</td>
<td style="text-align:left">防止<strong>拥有该成员的</strong>类和成员被重命名</td>
</tr>
</tbody>
</table>
<h3 id="2-保持元素不参与混淆的规则">2. 保持元素不参与混淆的规则</h3>
<p>形如：</p>
<pre><code>[保持命令] [类] {
    [成员] 
}
</code></pre>
<p>“类”代表类相关的限定条件，它将最终定位到某些符合该限定条件的类。它的内容可以使用：</p>
<ul>
<li>具体的类</li>
<li>访问修饰符（public、protected、private）</li>
<li>通配符*，匹配任意长度字符，但不含包名分隔符(.)</li>
<li>通配符**，匹配任意长度字符，并且包含包名分隔符(.)<br>
- extends，即可以指定类的基类<br>
- implement，匹配实现了某接口的类</li>
<li>$，内部类</li>
</ul>
<p>“成员”代表类成员相关的限定条件，它将最终定位到某些符合该限定条件的类成员。它的内容可以使用：</p>
<ul>
<li><code>&lt;init&gt;</code> 匹配所有构造器</li>
<li><code>&lt;fields&gt;</code> 匹配所有域</li>
<li><code>&lt;methods&gt;</code> 匹配所有方法</li>
<li>通配符*，匹配任意长度字符，但不含包名分隔符(.)</li>
<li>通配符**，匹配任意长度字符，并且包含包名分隔符(.)</li>
<li>通配符***，匹配任意参数类型<br>
- …，匹配任意长度的任意类型参数。比如 <code>void test(…)</code> 就能匹配任意 <code>void test(String a)</code> 或者是 <code>void test(int a, String b)</code> 这些方法。</li>
<li>访问修饰符（public、protected、private）</li>
</ul>
<p>举个例子，假如需要将 <code>name.huihui.test</code>包下所有继承 <code>Activity</code> 的 <code>public</code> 类及其构造函数都保持住，可以这样写：</p>
<pre><code>-keep public class name.huihui.test.** extends Android.app.Activity {
    &lt;init&gt;
}
</code></pre>
<h3 id="3-常用的自定义混淆规则">3. 常用的自定义混淆规则</h3>
<ul>
<li>不混淆某个类</li>
</ul>
<pre><code>-keep public class name.huihui.example.Test { *; }
</code></pre>
<ul>
<li>不混淆某个包所有的类</li>
</ul>
<pre><code>-keep class name.huihui.test.** { *; }
</code></pre>
<ul>
<li>不混淆某个类的子类</li>
</ul>
<pre><code>-keep public class * extends name.huihui.example.Test { *; }
</code></pre>
<ul>
<li>不混淆所有类名中包含了“model”的类及其成员</li>
</ul>
<pre><code>-keep public class **.*model*.** {*;}
</code></pre>
<ul>
<li>不混淆某个接口的实现</li>
</ul>
<pre><code>-keep class * implements name.huihui.example.TestInterface { *; }
</code></pre>
<ul>
<li>不混淆某个类的构造方法</li>
</ul>
<pre><code>-keepclassmembers class name.huihui.example.Test { 
    public &lt;init&gt;(); 
}
</code></pre>
<ul>
<li>不混淆某个类的特定的方法</li>
</ul>
<pre><code>-keepclassmembers class name.huihui.example.Test { 
    public void test(java.lang.String); 
}
</code></pre>
<h2 id="四-自定义资源保持规则">四、自定义资源保持规则</h2>
<h3 id="1-keepxml">1. keep.xml</h3>
<p>用<code>shrinkResources true</code>开启资源压缩后，所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留，在 <code>res/raw/</code>路径下创建一个 xml 文件，例如 <code>keep.xml</code>。</p>
<p>通过一些属性的设置可以实现定义资源保持的需求，可配置的属性有：<br>
- <code>tools:keep</code> 定义哪些资源需要被保留（资源之间用“,”隔开）<br>
- <code>tools:discard</code> 定义哪些资源需要被移除（资源之间用“,”隔开）<br>
- <code>tools:shrinkMode</code> 开启严格模式</p>
<p>当代码中通过 <code>Resources.getIdentifier()</code> 用动态的字符串来获取并使用资源时，普通的资源引用检查就可能会有问题。例如，如下代码会导致所有以“img_”开头的资源都被标记为已使用。</p>
<pre><code>String name = String.format(&quot;img_%1d&quot;, angle + 1);
res = getResources().getIdentifier(name, &quot;drawable&quot;, getPackageName());
</code></pre>
<p>我们可以设置 <code>tools:shrinkMode</code> 为 <code>strict</code> 来开启严格模式，使只有确实被使用的资源被保留。</p>
<p>以上就是自定义资源保持规则相关的配置，举个例子：</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;resources xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    tools:keep=&quot;@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*&quot;
    tools:discard=&quot;@layout/unused2&quot;
    tools:shrinkMode=&quot;strict&quot;/&gt;
</code></pre>
<h3 id="2-移除替代资源">2. 移除替代资源</h3>
<p>一些替代资源，例如多语言支持的 <code>strings.xml</code>，多分辨率支持的 <code>layout.xml</code> 等，在我们不需要使用又不想删除掉时，可以使用资源压缩将它们移除。</p>
<p>我们使用 <code>resConfig</code> 属性来指定需要支持的属性，例如</p>
<pre><code>android {
    defaultConfig {
        ...
        resConfigs &quot;en&quot;, &quot;fr&quot;
    }
}
</code></pre>
<p>其他未显式声明的语言资源将被移除。</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://developer.android.com/studio/build/shrink-code.html">Shrink Your Code and Resources</a></li>
<li><a href="http://proguard.sourceforge.net/">proguard</a></li>
<li><a href="http://blog.csdn.net/sinyu890807/article/details/50451259">Android安全攻防战，反编译与混淆技术完全解析（下）</a></li>
<li><a href="http://www.jianshu.com/p/7436a1a32891">Android混淆从入门到精通</a></li>
<li><a href="http://rensanning.iteye.com/blog/2224635">Android代码混淆之ProGuard</a></li>
</ul>
<h2 id="附录">附录</h2>
<ol>
<li><code>proguard-android.txt</code>文件内容</li>
</ol>
<pre><code>#包名不混合大小写
-dontusemixedcaseclassnames

#不跳过非公共的库的类
-dontskipnonpubliclibraryclasses

#混淆时记录日志
-verbose

#关闭预校验
-dontpreverify

#不优化输入的类文件
-dontoptimize

#保护注解
-keepattributes *Annotation*

#保持所有拥有本地方法的类名及本地方法名
-keepclasseswithmembernames class * {
    native &lt;methods&gt;;
}

#保持自定义View的get和set相关方法
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

#保持Activity中View及其子类入参的方法
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

#枚举
-keepclassmembers enum * {
    **[] $VALUES;
    public *;
}

#Parcelable
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

#R文件的静态成员
-keepclassmembers class **.R$* {
    public static &lt;fields&gt;;
}

-dontwarn android.support.**

#keep相关注解
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep &lt;methods&gt;;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep &lt;fields&gt;;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep &lt;init&gt;(...);
}
</code></pre>
<h2 id="写在最后">写在最后</h2>
<p>十分感谢你能看到最后，因本人水平有限，如有错漏还请不吝赐教，在此先行谢过。</p>
<p>同时，假如有这方面的困惑或者讨论，也十分欢迎联系我。</p>
<p>正文引用到此结束，文章有一张图片，实在是没找到，所以一直是空着的。</p>
<h2 id="我有话说">我有话说</h2>
<p>学了一波，发现其实 ProGuard 根本不难，而且，实际项目中，也不会引起线上错误日志调试困难，因为 ProGuard 完成后都会生成 mapping 文件，mapping 文件记录着所有符号表前后映射关系，借助这个文件，许多错误日志收集平台都能在线上反向解析出错误相关的源代码，现在几乎所有平台都会支持上传 mapping 文件。所以我现在都后悔学晚了。下次得抓紧。</p>
<p>本文封面图：Photo by <a href="https://unsplash.com/@ph_an_tom?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Duangphorn Wiriya</a> on <a href="https://unsplash.com/search/photos/confuse?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>

            </div>
          </article>
        </div>
    
        
          <div class="next-post">
            <div class="next">下一篇</div>
            <a href="https://cherrylover.github.io/post/yong-motionlayout-lai-zuo-guo-du-dong-hua/">
              <h3 class="post-title">
                用 MotionLayout 来做过渡动画
              </h3>
            </a>
          </div>  
        

        
          
            <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>

<div id="gitalk-container"></div>

<script>

  var gitalk = new Gitalk({
    clientID: 'f5cc70d7d50fba65cf69',
    clientSecret: '1d018e38ce9cdd399682bdd76690decfef00a051',
    repo: 'CherryLover.github.io',
    owner: 'CherryLover',
    admin: ['CherryLover'],
    id: location.pathname,      // Ensure uniqueness and length less than 50
    distractionFreeMode: false  // Facebook-like distraction free mode
  })

  gitalk.render('gitalk-container')

</script>

          

          
        
    
        <div class="site-footer">
  Powered by <a href="https://github.com/getgridea/gridea" target="_blank">Gridea</a>
</div>

<script>
  hljs.initHighlightingOnLoad()
</script>

      </div>
    </div>
  </body>
</html>
